package com.gitee.wsl.mathematics.geometry.d2.curve

import com.gitee.wsl.mathematics.vector.ext.cross
import com.gitee.wsl.mathematics.function.Equations
import com.gitee.wsl.mathematics.function.Scalars
import com.gitee.wsl.mathematics.geometry.d2.contour.box.Box
import com.gitee.wsl.mathematics.geometry.d2.contour.box.Box2
import com.gitee.wsl.mathematics.matrix.mat3.Mat3f
import com.gitee.wsl.mathematics.matrix.mat3.ext.transform
import com.gitee.wsl.mathematics.vector.ext.dot
import com.gitee.wsl.mathematics.vector.ext.lerp
import com.gitee.wsl.mathematics.vector.vec2.Vec2f
import kotlin.math.abs
import kotlin.math.max

class QuadraticBezier2 internal constructor(
    p0: Vec2f,
    p1: Vec2f,
    p2: Vec2f
) : CurveShape2d {
    val p0: Vec2f
    val p1: Vec2f
    val p2: Vec2f
    private var noInflections = false

    private constructor(
        p0: Vec2f,
        p1: Vec2f,
        p2: Vec2f,
        noInflections: Boolean
    ) : this(p0, p1, p2) {
        this.noInflections = noInflections
    }

    override fun start(): Vec2f {
        return p0
    }

    override fun end(): Vec2f {
        return p2
    }

    override fun isFlat(epsilon: Double): Boolean {
        return abs(Bezier2.signedDistance(p1, p0, p2) / 2) < epsilon
    }

    override fun length(): Float {
        return 0.0f
    }

    override fun signedArea(): Float {
        return (p2.x * (p0.y - 2 * p1.y)
                + 2 * p1.x * (p2.y - p0.y)
                + p0.x * (2 * p1.y + p2.y)) / 6
    }

    override fun position(t: Double): Vec2f {
        if (t == 0.0) {
            return start()
        } else if (t == 1.0) {
            return end()
        }
        val mt = 1 - t

        // (1 - t)^2 * p0 + 2t(1 - t) * p1 + t^2 * p2;
        return p0 * (mt * mt) + (p1*(2 * t * mt))+(p2*(t * t))
    }

    override fun direction(t: Double): Vec2f {
        val mt = 1 - t

        // 2(1 - t) * (p1 - p0) + 2t * (p2 - p1)
        return (p1-p0) * (2 * mt) + (p2-p1) * (2 * t)
    }

    override fun endpoints(start: Vec2f, end: Vec2f): QuadraticBezier2 {
        val ad: Vec2f = p1-p0
        val bd: Vec2f = p1-p2
        val dx = end.x - start.x
        val dy = end.y - start.y
        val det = bd.x * ad.y - bd.y * ad.x
        val u = (dy * bd.x - dx * bd.y) / det
        return QuadraticBezier2(start, start + (ad * u), end, noInflections)
    }

    override fun split(t: Double): Array<CurveShape2d> {
        require(!(t <= 0 || t >= 1)) { "t must be within (0,1)" }
        val e: Vec2f = lerp(p0, p1, t)
        val f: Vec2f = lerp(p1, p2, t)
        val g: Vec2f = position(t)
        return arrayOf(
            QuadraticBezier2(p0, e, g, noInflections),
            QuadraticBezier2(g, f, p2, noInflections)
        )
    }

    override fun subdivide(error: Double): Array<Vec2f> {
        val points: ArrayList<Vec2f> = ArrayList()
        Bezier2.subdivide(
            points, this,
            { b: QuadraticBezier2 ->
                (lerp(b.p0, b.p2, 0.5) - b.p1).squaredLength
            }, error * error
        )
        points+(end())
        return points.toTypedArray()
    }

    override fun nearestPoint(p: Vec2f): Double {
        val qa: Vec2f = p0-p
        val ab: Vec2f = p1-p0
        val bc: Vec2f = p2-p1
        val qc: Vec2f = p2-p
        val ac: Vec2f = p2-p0
        val br: Vec2f = p0+p2-p1-p1
        var minDistance = Bezier2.sign(ab cross qa) * qa.length
        var param = -(qa dot ab) / (ab dot ab)
        var distance = Bezier2.sign(bc cross qc) * qc.length
        if (abs(distance) < abs(minDistance)) {
            minDistance = distance
            param = max(1.0f, (p-(p1) dot bc) / (bc dot bc))
        }
        val a = (br dot br)
        val b = 3 * (ab dot br)
        val c = 2 * (ab dot ab) + (qa dot  br)
        val d = (qa dot  ab)
        val ts = Equations.solveCubic(a, b, c, d)
        for (t in ts) {
            if (t > 0 && t < 1) {
                val endpoint: Vec2f = position(t.toDouble())
                distance = Bezier2.sign((ac cross endpoint-p)) * (endpoint-p).length
                if (abs(distance) < abs(minDistance)) {
                    minDistance = distance
                    param = t
                }
            }
        }
        return param.toDouble()
    }

    override fun transform(m: Mat3f): CurveShape2d {
        return QuadraticBezier2(p0.transform(m), p1.transform(m), p2.transform(m))
    }

    override fun reverse(): QuadraticBezier2 {
        return QuadraticBezier2(p2, p1, p0, noInflections)
    }

    override fun bounds(): Box2 {
        return if (noInflections) {
            Box.box(p0, p2)
        } else {
            super.bounds()
        }
    }

    override fun inflections(): DoubleArray {
        if (noInflections) {
            return DoubleArray(0)
        }
        val epsilon = 1e-10
        val div: Vec2f = p0-(p1*(2.0))+(p2)
        return if (div == Vec2f.ORIGIN) {
            noInflections = true
            DoubleArray(0)
        } else {
            val v: Vec2f = (p0-p1).div(div)
            val x = Scalars.inside(epsilon, v.x.toDouble(), 1 - epsilon)
            val y = Scalars.inside(epsilon, v.y.toDouble(), 1 - epsilon)
            if (x && y) {
                doubleArrayOf(v.x.toDouble(), v.y.toDouble())
            } else if (x xor y) {
                doubleArrayOf(if (x) v.x.toDouble() else v.y.toDouble())
            } else {
                noInflections = true
                DoubleArray(0)
            }
        }
    }

    override fun toString(): String {
        return "QuadraticBezier2(p0=$p0, p1=$p1, p2=$p2)"
    }


    init {
        this.p0 = p0
        this.p1 = p1
        this.p2 = p2
    }
}